﻿using Microsoft.AspNetCore.Mvc;
using Surging.Core.ApiGateWay;
using Surging.Core.ApiGateWay.OAuth;
using Surging.Core.CPlatform;
using Surging.Core.CPlatform.Filters.Implementation;
using Surging.Core.CPlatform.Routing;
using Surging.Core.ProxyGenerator;
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using GateWayAppConfig = Surging.Core.ApiGateWay.AppConfig;
using Surging.Core.CPlatform.Transport.Implementation;
using Surging.Core.CPlatform.Routing.Template;
using System.Linq;
using System.Reflection;
using Surging.Core.CPlatform.Utilities;
using Surging.Core.Common;

namespace Dora.ApiGateway.Controllers
{
    public class ServicesController : Controller
    {
        private readonly IServiceProxyProvider _serviceProxyProvider;
        private readonly IServiceRouteProvider _serviceRouteProvider;
        private readonly IAuthorizationServerProvider _authorizationServerProvider;

        public ServicesController(IServiceProxyProvider serviceProxyProvider,
            IServiceRouteProvider serviceRouteProvider,
            IAuthorizationServerProvider authorizationServerProvider)
        {
            _serviceProxyProvider = serviceProxyProvider;
            _serviceRouteProvider = serviceRouteProvider;
            _authorizationServerProvider = authorizationServerProvider;
        }

        public async Task<ServiceResult<object>> Path([FromServices]IServicePartProvider servicePartProvider, string path, [FromBody]Dictionary<string, object> model)
        {
            string serviceKey = this.Request.Query["servicekey"];
            model = InterceptHttpHeader(model);//拦截header中的参数，然后加入到请求参数和RpContext中
            ServiceResult<object> result = ServiceResult<object>.Create(false, null);
            path = path.IndexOf("/", StringComparison.Ordinal) < 0 ? $"/{path}" : path;
            #region 如果调用的注销方法，调用登出
            if (string.Compare(path.ToLower(), GateWayAppConfig.LogoutEndpointPath, true) == 0)
            {
                path = GateWayAppConfig.DestoryAuthenticationRoutePath; //如果是api/outh2/logout的话，替换成oauthhost中的失效方法
                var route1 = await _serviceRouteProvider.GetRouteByPathRegex(path);
                if (!string.IsNullOrEmpty(serviceKey))
                {
                    result = ServiceResult<object>.Create(true, await _serviceProxyProvider.Invoke<object>(model, route1.ServiceDescriptor.RoutePath, serviceKey));
                }
                else
                {
                    result = ServiceResult<object>.Create(true, await _serviceProxyProvider.Invoke<object>(model, route1.ServiceDescriptor.RoutePath));
                }
                result.StatusCode = (int)ServiceStatusCode.Success;
                return result;
            }
            #endregion
            path = string.Compare(path.ToLower(), GateWayAppConfig.TokenEndpointPath, true) == 0 ?
                GateWayAppConfig.AuthorizationRoutePath : path.ToLower();
            var route = await _serviceRouteProvider.GetRouteByPathRegex(path);
            if (route is null)
                return new ServiceResult<object> { IsSucceed = false, StatusCode = (int)ServiceStatusCode.Http405Endpoint, Message = "405 HTTP route Not Supported" };
            var httpMethods = route.ServiceDescriptor.HttpMethod();
            if (!string.IsNullOrEmpty(httpMethods) && !httpMethods.Contains(Request.Method))
                return new ServiceResult<object> { IsSucceed = false, StatusCode = (int)ServiceStatusCode.Http405Endpoint, Message = "405 HTTP Method Not Supported" };
            //无法访问的服务则直接拦截
            if (!GetAllowRequest(route)) return new ServiceResult<object> { IsSucceed = false, StatusCode = (int)ServiceStatusCode.RequestError, Message = "Request error" };
            //服务聚合
            if (servicePartProvider.IsPart(path))
            {
                result = ServiceResult<object>.Create(true, await servicePartProvider.Merge(path, model));
                result.StatusCode = (int)ServiceStatusCode.Success;
            }
            else
            {
                var auth = OnAuthorization(route, model);
                result = auth.Item2;
                if (auth.Item1)
                {
                    #region token请求部分 返回token或者异常的信息
                    if (path == GateWayAppConfig.AuthorizationRoutePath)
                    {
                        //var tokenDto = await _authorizationServerProvider.GenerateTokenCredential(model);
                        var tokenDto = await _serviceProxyProvider.Invoke<object>(model, GateWayAppConfig.AuthorizationRoutePath,
                                  GateWayAppConfig.AuthorizationServiceKey);
                        if (tokenDto != null)
                        {
                            result = ServiceResult<object>.Create(true, tokenDto);
                            result.StatusCode = (int)ServiceStatusCode.Success;
                        }
                        else
                        {
                            result = new ServiceResult<object>
                            {
                                IsSucceed = false,
                                StatusCode = (int)ServiceStatusCode.AuthorizationFailed,
                                Message = "生成token失败" //or "Invalid authentication credentials"
                            };
                        }
                    }
                    #endregion
                    else
                    {

                        if (String.Compare(route.ServiceDescriptor.RoutePath, path, true) != 0)
                        {
                            var pamars = RouteTemplateSegmenter.Segment(route.ServiceDescriptor.RoutePath, path);
                            foreach (KeyValuePair<string, object> item in pamars)
                            {
                                model.Add(item.Key, item.Value);
                            }
                        }
                        if (!string.IsNullOrEmpty(serviceKey))
                        {

                            result = ServiceResult<object>.Create(true, await _serviceProxyProvider.Invoke<object>(model, route.ServiceDescriptor.RoutePath, serviceKey));
                            result.StatusCode = (int)ServiceStatusCode.Success;
                        }
                        else
                        {
                            result = ServiceResult<object>.Create(true, await _serviceProxyProvider.Invoke<object>(model, route.ServiceDescriptor.RoutePath));
                            result.StatusCode = (int)ServiceStatusCode.Success;
                        }
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// 拦截header中的参数，然后加入到请求参数和RpContext中
        /// </summary>
        /// <param name="model"></param>
        private Dictionary<string, object> InterceptHttpHeader(Dictionary<string, object> model)
        {
            model = model ?? new Dictionary<string, object>();            
            foreach (string n in this.Request.Query.Keys)
            {
                model[n] = this.Request.Query[n].ToString();
            }
            var token = HttpContext.Request.Headers.ContainsKey("Authorization") ? HttpContext.Request.Headers["Authorization"].ToString() : "";
            var systemKey = HttpContext.Request.Headers.ContainsKey("systemKey") ? HttpContext.Request.Headers["systemKey"].ToString() : "";
            var ip = Request.HttpContext.Connection.RemoteIpAddress.MapToIPv4().ToString();
            model["token"] = token;
            model["systemKey"] = systemKey;
            try
            {
                RpcContext.GetContext().SetAttachment("token", token);
                RpcContext.GetContext().SetAttachment("systemKey", systemKey);
                RpcContext.GetContext().SetAttachment("ip", ip); //加入访问ip
            }
            catch (Exception)
            {
                throw new Exception("token Or Systemkey has Exception for RpcContext");
            }
            return model;
        }


        private bool GetAllowRequest(ServiceRoute route)
        {
            return !route.ServiceDescriptor.DisableNetwork();
        }

        /// <summary>
        /// 鉴权  判断 微服务的接口中有无权限 拦截器，有的话鉴权验证，无的话返回true直接放行
        /// </summary>
        /// <param name="route"></param>
        /// <param name="model"></param>
        /// <returns></returns>
        private (bool, ServiceResult<object>) OnAuthorization(ServiceRoute route, Dictionary<string, object> model)
        {
            bool isSuccess = true;
            var serviceResult = ServiceResult<object>.Create(false, null);
            if (route.ServiceDescriptor.RoutePath == GateWayAppConfig.AuthorizationRoutePath) return new ValueTuple<bool, ServiceResult<object>>(isSuccess, serviceResult);
            if (route.ServiceDescriptor.EnableAuthorization())
            {
                #region token验证
                if (route.ServiceDescriptor.AuthType() == AuthorizationType.JWT.ToString())
                {
                    isSuccess = ValidateJwtAuthentication(route, model, ref serviceResult);
                }
                else
                {
                    isSuccess = ValidateAppSecretAuthentication(route, model, ref serviceResult);
                }
                #endregion
                #region auth鉴权
                if (!isSuccess) return new ValueTuple<bool, ServiceResult<object>>(isSuccess, serviceResult); ; //token不合法 直接返回不进行下面的验证
                if (route.ServiceDescriptor.Metadatas.TryGetValue("AuthKey", out var aukeys))//有authkey，进行鉴权
                {
                    string token = null;
                    if (model.ContainsKey("token"))
                    {
                        token = model["token"].ToString();
                    }
                    if (!string.IsNullOrWhiteSpace(token))
                    {
                        var authModel = new Dictionary<string, object> { { "authkeyStr", aukeys }, { "token", token } };
                        isSuccess = _serviceProxyProvider.Invoke<bool>(authModel, "api/oauthhost/oauthhost/verifypermissions").GetAwaiter().GetResult();
                        if (!isSuccess) serviceResult = new ServiceResult<object> { IsSucceed = false, StatusCode = (int)ServiceStatusCode.AuthorizationFailed, Message = "Invalid authentication key credentials" };
                    }
                    else
                    {
                        isSuccess = false;
                        serviceResult = new ServiceResult<object> { IsSucceed = false, StatusCode = (int)ServiceStatusCode.AuthorizationFailed, Message = "Invalid authentication  credentials" };
                    }
                }
                #endregion
            }
            return new ValueTuple<bool, ServiceResult<object>>(isSuccess, serviceResult);
        }

        /// <summary>
        /// 调用oauth服务去做鉴权（token鉴权）
        /// </summary>
        /// <param name="route"></param>
        /// <param name="model"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public bool ValidateJwtAuthentication(ServiceRoute route, Dictionary<string, object> model, ref ServiceResult<object> result)
        {
            bool isSuccess = true;
            var author = HttpContext.Request.Headers["Authorization"];
            if (author.Count > 0)
            {
                isSuccess = _authorizationServerProvider.ValidateClientAuthentication(author).Result;
                //Console.WriteLine(@"SEND-TOKEN=" + author);
                //token不合法
                if (!isSuccess)
                {
                    result = new ServiceResult<object> { IsSucceed = false, StatusCode = (int)ServiceStatusCode.AuthorizationFailed, Message = "Invalid authentication credentials" };
                }
                else
                {
                    //var payload = _serviceProxyProvider.Invoke<string>(model, "api/oauthhost/oauthhost/getpayload").GetAwaiter().GetResult();
                    var payload = _authorizationServerProvider.GetPayloadString(author);
                    RpcContext.GetContext().SetAttachment("payload", payload);
                    if (model.Count > 0)
                    {
                        var keyValue = model.FirstOrDefault();
                        if (!(keyValue.Value is IConvertible) || !typeof(IConvertible).GetTypeInfo().IsAssignableFrom(keyValue.Value.GetType()))
                        {
                            dynamic instance = keyValue.Value;
                            //instance.Payload = payload.ToString();
                            model.Remove(keyValue.Key);
                            model.Add(keyValue.Key, instance);
                        }
                    }
                }
            }
            else
            {
                result = new ServiceResult<object> { IsSucceed = false, StatusCode = (int)ServiceStatusCode.RequestError, Message = "no token" };
                isSuccess = false;
            }
            return isSuccess;
        }

        private bool ValidateAppSecretAuthentication(ServiceRoute route,
            Dictionary<string, object> model, ref ServiceResult<object> result)
        {
            bool isSuccess = true;
            DateTime time;
            var author = HttpContext.Request.Headers["Authorization"];

            if (model.ContainsKey("timeStamp") && author.Count > 0)
            {
                if (long.TryParse(model["timeStamp"].ToString(), out long timeStamp))
                {
                    time = DateTimeConverter.UnixTimestampToDateTime(timeStamp);
                    var seconds = (DateTime.Now - time).TotalSeconds;
                    if (seconds <= 3560 && seconds >= 0)
                    {
                        if (GetMD5($"{route.ServiceDescriptor.Token}{time.ToString("yyyy-MM-dd hh:mm:ss") }") != author.ToString())
                        {
                            result = new ServiceResult<object> { IsSucceed = false, StatusCode = (int)ServiceStatusCode.AuthorizationFailed, Message = "Invalid authentication credentials" };
                            isSuccess = false;
                        }
                    }
                    else
                    {
                        result = new ServiceResult<object> { IsSucceed = false, StatusCode = (int)ServiceStatusCode.AuthorizationFailed, Message = "Invalid authentication credentials" };
                        isSuccess = false;
                    }
                }
                else
                {
                    result = new ServiceResult<object> { IsSucceed = false, StatusCode = (int)ServiceStatusCode.AuthorizationFailed, Message = "Invalid authentication credentials" };
                    isSuccess = false;
                }
            }
            else
            {
                result = new ServiceResult<object> { IsSucceed = false, StatusCode = (int)ServiceStatusCode.RequestError, Message = "Request error" };
                isSuccess = false;
            }
            return isSuccess;
        }

        public static string GetMD5(string encypStr)
        {
            try
            {
                var md5 = MD5.Create();
                var bs = md5.ComputeHash(Encoding.UTF8.GetBytes(encypStr));
                var sb = new StringBuilder();
                foreach (byte b in bs)
                {
                    sb.Append(b.ToString("X2"));
                }
                //所有字符转为大写
                return sb.ToString().ToLower();
            }
            catch (Exception e)
            {
                Console.Error.WriteLine(e.StackTrace);
                return null;
            }
        }
    }
}
